/*
 * $Id: RootPane.java 52 2009-04-12 10:22:42Z meyer@alphatec.de $
 * 
 *
 * Copyright 2009 DeltaDesk
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package deltadesk.gui.deltagui.app;

import java.awt.AWTException;
import java.awt.Container;
import java.awt.Cursor;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import deltadesk.gui.deltagui.util.GUIUtils;
import deltadesk.gui.deltagui.widget.Composite;
import deltadesk.gui.deltagui.widget.Desktop;

/**
 * RootPane.
 * 
 * Use active rendering
 * 
 * @author Andreas Meyer
 * @version $Revision: 52 $
 */
public class RootPane extends Container implements Runnable {

	/** Default serial version UID. */
	private static final long serialVersionUID = 1L;
	
	/** The sleep time for the intervals waiting for the graphics device. */
	private static final int SLEEP_TIME_WAIT_FOR_GRAPHICS = 50;

	/** The default desired frames per second. */
	private static final int DEFAULT_DESIRED_FPS = 25;
	
	/** The logger. */
	private static final Log log = LogFactory.getLog(RootPane.class);

	/** The running state of the thread. */
	private boolean running = true;

	/** The robot. */
	private Robot robot;
	
	/** The desktop widget. */
	private Desktop desktop;
	
	/** The page flipping buffer strategy. */
	private BufferStrategy bs;

	/** The desired frames per second. */
	private int desiredFPS = DEFAULT_DESIRED_FPS;


	/**
	 * Construct the root pane.
	 * 
	 * @param exithandler
	 * @param mainComposite
	 */
	public RootPane(final ExitHandler exithandler,
			        final Composite mainComposite) {
		
		desktop = new Desktop(mainComposite);

		showCursor(false);
		
		// monitor key events
		setFocusTraversalKeysEnabled(false);
		addKeyListener(new KeyAdapter() {

			public void keyPressed(final KeyEvent e) {
				
				// Handle Ctrl-C or ESC for exiting the application
				if(e.getKeyCode() == KeyEvent.VK_C && e.isControlDown() ||
				   e.getKeyCode() == KeyEvent.VK_ESCAPE) {
					exithandler.exit();
				} else {
					desktop.postKeyEvent(e);
				}
			}
			public void keyReleased(final KeyEvent e) {
				desktop.postKeyEvent(e);
			}
			public void keyTyped(final KeyEvent e) {
				desktop.postKeyEvent(e);
			}
		});
		
		addMouseMotionListener(new MouseMotionAdapter() {

			public void mouseMoved(final MouseEvent me) {
				
				// Keep mouse pointer inside the content pane to prevent
				// showing the cursor outside the edges
				robot.mouseMove(10, 10);
			}
		});
	}
	
	/**
	 * Show or hide the mouse cursor.
	 * 
	 * @param sc
	 */
	public void showCursor(final boolean sc) {
		
		if(!sc) {

			// create blank cursor
			final BufferedImage img = new BufferedImage(3, 3,
					BufferedImage.TYPE_INT_ARGB);
			final Cursor blankCursor = Toolkit.getDefaultToolkit()
					.createCustomCursor(img, new Point(0, 0), "blank");

			// set the blank cursor
			setCursor(blankCursor);

			// create mouse controller
			try {
				robot = new Robot();
			} catch (AWTException aex) {
				robot = null;
			}
		} else {
			setCursor(Cursor.getDefaultCursor());
		}
	}
	
	/**
	 * @return the desktop
	 */
	public Desktop getDesktop() {
		return desktop;
	}
	
	public void setBufferStrategy(final BufferStrategy bs) {
		this.bs = bs;
	}

	/**
	 * @see java.lang.Runnable#run()
	 */
	public void run() {
		
		waitForGraphicsDevice();
		renderLoop();
	}

	/**
	 * @param running the running to set
	 */
	public void setRunning(final boolean running) {
		this.running = running;
	}

	/**
	 * @return the desiredFPS
	 */
	public int getDesiredFPS() {
		return desiredFPS;
	}
	
	/**
	 * @param desiredFPS the desiredFPS to set
	 */
	public void setDesiredFPS(final int desiredFPS) {
		this.desiredFPS = desiredFPS;
	}

	/**
	 * Wait until the graphics device is available.
	 * 
	 */
	private void waitForGraphicsDevice() {
		
		Graphics g;
		while(running) {
			g = getGraphics();
			if(g != null && bs != null) {
				break;
			}
			
			GUIUtils.sleep(SLEEP_TIME_WAIT_FOR_GRAPHICS);
		} // end while

		desktop.setSize(getWidth(), getHeight());
	}
	
	/**
	 * Our render loop.
	 * 
	 */
	private void renderLoop() {
		
		// enable receiving key events
		requestFocus();

		// timer stuff
		long timeDiff, sleepTime;
		long period = 1000 / desiredFPS;
		long beforeTime = System.currentTimeMillis();
		while(running) {

			// set up rendering
			final Graphics2D g2 = (Graphics2D) getGraphics();
			g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
					RenderingHints.VALUE_ANTIALIAS_ON);
			g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
					RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

			// render to offscreen image
			Graphics2D dbG2 = (Graphics2D)bs.getDrawGraphics();
			dbG2.setClip(new Rectangle(0, 0, getWidth()-1, getHeight()-1));
			desktop.render(dbG2);
			dbG2.dispose();
			
			// flip buffer to screen
			if(!bs.contentsLost()) {
				bs.show();
			} else {
				log.info("LOST CONTENTS");
			}

			// sync buffer and clean up
			Toolkit.getDefaultToolkit().sync();
			g2.dispose();
			
			// calculate sleep time and sleep for a while
			// so that the VM can do other stuff
			if(period != -1) {
				timeDiff = System.currentTimeMillis() - beforeTime;

				sleepTime = period - timeDiff;
				if(sleepTime < 0) {
					sleepTime = 5;
				}
				GUIUtils.sleep(sleepTime);
				
				beforeTime = System.currentTimeMillis();
			}

		} // end while
	}

}
